/* eslint-disable no-param-reassign */
import { DomElement } from "../../src/model/dom";
import Style from "../model/Style";

// 排版
function getStyle(element: DomElement): Style {
  if (!element.style) {
    element.style = {};
  }
  Object.keys(element.computedStyle).forEach((key: keyof Style) => {
    element.style[key] = element.computedStyle[key];
    if (element.style[key].match(/px$/)) {
      element.style[key] = `${parseInt(element.style[key], 10)}`;
    }
    if (element.style[key].toString().match(/[0-9]+\.$/)) {
      element.style[key] = `${parseInt(element.style[key], 10)}`;
    }
  });
  return element.style;
}

/**
 * 根据已经计算出 css dom 树计算出每个元素的具体位置
 * @param dom 已经计算出 css 的 dom 树
 * @return 返回一个已经计算好位置的 dom 树
 */
export default function layout(element: DomElement): DomElement {
  if (!element.computedStyle) {
    return element;
  }
  const elementStyle = getStyle(element);
  if (elementStyle.display !== "flex") {
    return element;
  }

  const items = element.children.filter((e) => {
    return e.type === "element";
  });

  // 处理 width 或者 height 没有写值的情况，一般是会将父元素撑开刚好包裹所有子元素
  ["width", "height"].forEach((size: keyof Style) => {
    if (elementStyle[size] === "auto" || elementStyle[size] === "") {
      elementStyle[size] = null;
    }
  });
  if (!elementStyle["flex-direction"] || elementStyle["flex-direction"] === "auto") {
    elementStyle["flex-direction"] = "row";
  }
  if (!elementStyle["align-items"] || elementStyle["align-items"] === "auto") {
    elementStyle["align-items"] = "stretch";
  }
  if (!elementStyle["justify-content"] || elementStyle["justify-content"] === "auto") {
    elementStyle["justify-content"] = "flex-start";
  }
  if (!elementStyle["flex-wrap"] || elementStyle["flex-wrap"] === "auto") {
    elementStyle["flex-wrap"] = "nowrap";
  }
  if (!elementStyle["align-content"] || elementStyle["align-content"] === "auto") {
    elementStyle["align-content"] = "stretch";
  }
  // 将容器的 width、height、left、right、top、bottom 抽象成「主轴」和「交叉轴」
  let mainSize: keyof Style;
  let mainStart: keyof Style;
  let mainEnd: keyof Style;
  let mainSign: number;
  let mainBase: number;
  let crossSize: keyof Style;
  let crossStart: keyof Style;
  let crossEnd: keyof Style;
  let crossSign: number;
  let crossBase: number;
  if (elementStyle["flex-direction"] === "row") {
    mainSize = "width";
    mainStart = "left";
    mainEnd = "right";
    mainSign = +1; // 方向抽象成数值，便于计算
    mainBase = 0;

    crossSize = "height";
    crossStart = "top";
    crossEnd = "bottom";
  } else if (elementStyle["flex-direction"] === "row-reverse") {
    mainSize = "width";
    mainStart = "right";
    mainEnd = "left";
    mainSign = -1; // 方向抽象成数值，便于计算
    mainBase = +elementStyle.width;

    crossSize = "height";
    crossStart = "top";
    crossEnd = "bottom";
  } else if (elementStyle["flex-direction"] === "column") {
    mainSize = "height";
    mainStart = "top";
    mainEnd = "bottom";
    mainSign = +1; // 方向抽象成数值，便于计算
    mainBase = 0;

    crossSize = "width";
    crossStart = "left";
    crossEnd = "right";
  } else if (elementStyle["flex-direction"] === "column-reverse") {
    mainSize = "height";
    mainStart = "bottom";
    mainEnd = "top";
    mainSign = -1; // 方向抽象成数值，便于计算
    mainBase = +elementStyle.height;

    crossSize = "width";
    crossStart = "left";
    crossEnd = "right";
  }
  if (elementStyle["flex-wrap"] === "wrap-reverse") {
    [crossStart, crossEnd] = [crossEnd, crossStart];
    crossSign = -1;
  } else {
    crossSign = +1;
    crossBase = 0;
  }

  // auto sizing
  let isAutoMainSize = false;
  if (!elementStyle[mainSize]) {
    elementStyle[mainSize] = "0";
    items.forEach((item) => {
      const itemStyle = getStyle(item);
      if (itemStyle[mainSize] !== null && itemStyle[mainSize] !== undefined) {
        elementStyle[mainSize] = (+elementStyle[mainSize] + +itemStyle[mainSize]).toString();
      }
    });
    isAutoMainSize = true;
  }

  let flexLine: any = []; // flex一行
  const flexLines: any[] = [flexLine]; // flex所有行
  // 主轴剩余空间
  let mainSpace = +elementStyle[mainSize];
  // 交叉轴剩余空间
  let crossSpace = 0;

  let flexTotal = 0;
  items.forEach((item) => {
    const itemStyle = getStyle(item);
    if (itemStyle.flex) {
      flexTotal += parseInt(itemStyle.flex, 10);
      flexLine.push(item);
    } else if (elementStyle["flex-wrap"] === "nowrap" || isAutoMainSize) {
      mainSpace = +mainSpace - +itemStyle[mainSize];
      if (itemStyle[crossSize] !== null && itemStyle[crossSize] !== undefined) {
        crossSpace = Math.max(crossSpace, +itemStyle[crossSize]);
      }
      flexLine.push(item);
    } else {
      if (+itemStyle[mainSize] > +elementStyle[mainSize]) {
        itemStyle[mainSize] = elementStyle[mainSize];
      }
      if (+itemStyle[mainSize] > +mainSpace) {
        flexLine.mainSpace = mainSpace;
        flexLine.crossSpace = crossSpace;

        flexLine = [item];
        flexLines.push(flexLine);

        mainSpace = +elementStyle[mainSize];
        crossSpace = 0;
      } else {
        flexLine.push(item);
      }
      if (itemStyle[crossSize] !== null && itemStyle[crossSize] !== undefined) {
        crossSpace = Math.max(crossSpace, +itemStyle[crossSize]);
      }
      mainSpace -= +itemStyle[mainSize];
    }
  });
  flexLine.flexTotal = flexTotal;
  flexLine.mainSpace = mainSpace;
  if (elementStyle["flex-wrap"] === "nowrap" || isAutoMainSize) {
    // 交叉轴有尺寸就直接使用，没有就用子元素中最大的
    flexLine.crossSpace =
      elementStyle[crossSize] !== undefined ? +elementStyle[crossSize] : crossSpace;
  } else {
    flexLine.crossSpace = crossSpace;
  }

  //  ------------------------- 计算主轴 ---------------------------------/
  flexLines.forEach((line) => {
    const lineMainSpace = Math.max(line.mainSpace, 0); // 处理掉剩余空间为负数
    let currentMainStart = mainBase;
    let gap = 0;
    if (line.flexTotal === 0) {
      if (element.style["justify-content"] === "flex-start") {
        currentMainStart = mainBase;
        gap = 0;
      } else if (element.style["justify-content"] === "flex-end") {
        currentMainStart = mainBase + mainSign * lineMainSpace;
        gap = 0;
      } else if (element.style["justify-content"] === "center") {
        currentMainStart = mainBase + mainSign * (lineMainSpace / 2);
        gap = 0;
      } else if (element.style["justify-content"] === "space-between") {
        currentMainStart = mainBase;
        gap = mainSign * (lineMainSpace / (line.length - 1));
      } else if (element.style["justify-content"] === "space-around") {
        gap = mainSign * (lineMainSpace / line.length);
        currentMainStart = mainBase + gap / 2;
      }
    }
    const scale = Math.min(+elementStyle[mainSize] / (+elementStyle[mainSize] - mainSpace), 1);
    // 计算容器内所有子元素的主轴尺寸
    line.forEach((item: DomElement) => {
      // 有 flex
      if (item.style.flex) {
        item.style[mainSize] = `${(lineMainSpace * +item.style.flex) / line.flexTotal}`;
      } else {
        item.style[mainSize] = `${+item.style[mainSize] * scale}`;
      }
      item.style[mainStart] = `${currentMainStart}`;
      item.style[mainEnd] = `${+item.style[mainStart] + mainSign * +item.style[mainSize]}`;
      currentMainStart = +item.style[mainEnd] + gap;
    });
  });

  //  ------------------------- 计算交叉轴 --------------------------------/
  if (!elementStyle[crossSize]) {
    crossSpace = 0;
    elementStyle[crossSize] = "0";
    for (let i = 0; i < flexLines.length; i += 1) {
      elementStyle[crossSize] = (+elementStyle[crossSize] + flexLines[i].crossSpace).toString();
    }
  } else {
    crossSpace = +elementStyle[crossSize];
    for (let i = 0; i < flexLines.length; i += 1) {
      crossSpace -= flexLines[i].crossSpace;
    }
  }

  if (elementStyle["flex-wrap"] === "wrap-reverse") {
    crossBase = +elementStyle[crossSize];
  } else {
    crossBase = 0;
  }

  let gap = 0;
  if (elementStyle["align-content"] === "flex-start") {
    crossBase += 0;
    gap = 0;
  } else if (elementStyle["align-content"] === "flex-end") {
    crossBase += crossSign * crossSpace;
    gap = 0;
  } else if (elementStyle["align-content"] === "center") {
    crossBase += crossSign * (crossSpace / 2);
    gap = 0;
  } else if (elementStyle["align-content"] === "space-between") {
    crossBase += 0;
    gap = crossSign * (crossSpace / (flexLines.length - 1));
  } else if (elementStyle["align-content"] === "space-around") {
    gap = crossSpace / flexLines.length;
    crossBase += crossSign * (gap / 2);
  } else if (elementStyle["align-content"] === "stretch") {
    crossBase += 0;
    gap = 0;
  }

  flexLines.forEach((line) => {
    // const lineCrossSize =
    //   elementStyle["align-content"] === "stretch"
    //     ? line.crossSpace + crossSpace / flexLines.length
    //     : line.crossSpace;
    let lineCrossSize = line.crossSpace;
    if (elementStyle["align-content"] === "stretch" && crossSpace > 0) {
      lineCrossSize = line.crossSpace + crossSpace / flexLines.length;
    }
    line.forEach((item: DomElement) => {
      // 子元素的 alignSelf 会覆盖父元素的 alignItems
      const itemAlign = item.style["align-self"] || elementStyle["align-items"];

      if (!item.style[crossSize]) {
        item.style[crossSize] = itemAlign === "stretch" ? lineCrossSize : 0;
      }

      if (itemAlign === "flex-start") {
        item.style[crossStart] = `${crossBase}`;
        item.style[crossEnd] = `${+item.style[crossStart] + crossSign * +item.style[mainSize]}`;
      } else if (itemAlign === "flex-end") {
        item.style[crossEnd] = `${crossBase + crossSign * lineCrossSize}`;
        item.style[crossStart] = `${+item.style[crossEnd] - crossSign * +item.style[crossSize]}`;
      } else if (itemAlign === "center") {
        item.style[crossStart] = `${
          crossBase + crossSign * ((lineCrossSize - +item.style[crossSize]) / 2)
        }`;
        item.style[crossEnd] = `${+item.style[crossStart] + crossSign * +item.style[crossSize]}`;
      } else if (itemAlign === "stretch") {
        item.style[crossStart] = `${crossBase}`;
        item.style[crossEnd] = `${
          crossBase + crossSign * (+item.style[crossSize] ? +item.style[crossSize] : lineCrossSize)
        }`;
        item.style[crossSize] = `${crossSign * (+item.style[crossEnd] - +item.style[crossStart])}`;
      }
    });
    crossBase += crossSign * (lineCrossSize + gap);
  });
  return element;
}
